package cn.simon.free.ratelimiter.core;


import cn.simon.free.ratelimiter.annotation.RateLimiter;
import cn.simon.free.ratelimiter.core.providers.CacheProviders;
import cn.simon.free.ratelimiter.core.providers.LocalCache;
import cn.simon.free.ratelimiter.core.providers.RedisCache;
import cn.simon.free.ratelimiter.utils.AllMethodRateLimiterUtils;
import cn.simon.free.ratelimiter.utils.SpringRateLimitUtils;
import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;

/**
 * 初始化生成所有的限流器对象,默认禁用(如果要使用分布式注解,必须在类上打上该{@link RateLimiter}注解)
 * 1. 如果注解打在类上,该类下有声明该 {@link RateLimiter}注解的方法都会被限流,
 * 如果方法上rate存在,就覆盖类上的rate/如果方法上的rate不存在,就用类上的rate
 * 2. 如果某一个方法不参与限流,不使用该注解即可
 * 3. 如果某一个方法声明了注解{@link RateLimiter},但是不想参与限流,参数optOut 设置为true
 *
 * @author simon
 * @date 2019/12/26 14:11
 * @copyright:
 * @modifier
 * @motto a no talent, people who have to study hard
 */
@Slf4j
@Component
@DependsOn(value = {"springUtils"})
@Configuration
@ConditionalOnProperty(prefix = "cn.glc.rate-limit", name = "enable", havingValue = "true")
public class RateInit {

    private static CacheProviders cacheProviders;
    private long num;

    public RateInit() {
        RateRedis rateRedis = SpringRateLimitUtils.getApplicationContext().getBean(RateRedis.class);
        if (Objects.isNull(rateRedis) || Strings.isNullOrEmpty(rateRedis.getAddress())) {
            cacheProviders = new LocalCache();
        } else {
            cacheProviders = new RedisCache();
        }
    }

    public static CacheProviders getCacheProvider() {
        return cacheProviders;
    }

    @PostConstruct
    public void init() throws ClassNotFoundException {
        Map<String, Object> beans = SpringRateLimitUtils.getApplicationContext().getBeansWithAnnotation(RateLimiter.class);
        validConsumer(beans);
        log.info("{}", beans);
    }

    private void validConsumer(Map<String, Object> map) throws ClassNotFoundException {
        Iterator var3 = map.entrySet().iterator();
        while (var3.hasNext()) {
            Map.Entry<String, Object> entry = (Map.Entry) var3.next();
            RateLimiter annotationOnBean = SpringRateLimitUtils.getApplicationContext().findAnnotationOnBean(entry.getKey(), RateLimiter.class);
            if (Objects.isNull(annotationOnBean)) continue;
            Class<?> aClass = ClassUtils.forName(entry.getValue().getClass().getName(), SpringRateLimitUtils.getApplicationContext().getClassLoader());
            //level one rate
            double rate_o = annotationOnBean.rate();
            //if optOut is true,current class no attend rate (all method no attend rate)
            if (Objects.equals(true, annotationOnBean.optOut())) {
                log.info("{} class no attend current limiting");
                continue;
            }
            Method[] methods = aClass.getDeclaredMethods();
            for (Method method : methods) {
                RateLimiter annotation = method.getAnnotation(RateLimiter.class);
                if (Objects.isNull(annotation)) continue;
                //use method hashCode value
                String unique = String.valueOf(method.hashCode());
                // if The method {@link RateLimiter} no exist, rate_t is ""
                double rate_t = annotation.rate();
                String rateName = annotation.name();
                if (rate_t == 0) putRate(rate_o, 0, unique, "");
                //level two rate
                if (Objects.equals(true, annotation.optOut())) {
                    log.info("{} method no attend current limiting", rateName);
                    continue;
                }
                putRate(rate_o, rate_t, unique, rateName);
            }
        }
        log.info("rateLimiter init over,nums ={}", num);
    }

    private void putRate(double rate_o, double rate_t, String unique, String rateName) {
        Rate.RateBuilder rateBuilder = Rate.builder().unique(unique).name(rateName).optOut(false);
        if (!Objects.isNull(rate_t) && rate_t != 0) {
            AllMethodRateLimiterUtils.put(unique, rateBuilder.rate(rate_t).build());
            num++;
            return;
        }
        if (!Objects.isNull(rate_o) && rate_o != 0) {
            AllMethodRateLimiterUtils.put(unique, rateBuilder.rate(rate_o).build());
            num++;
            return;
        }
        log.info("The class rate and method rate are empty,please check!");
    }

}
